import datetime
from bson import ObjectId
from utils.serializer import ModelSerializer
from mongoengine import Document as OriDocument, StringField, Q, DateTimeField, BooleanField, fields, ObjectIdField, \
    EmbeddedDocumentField, ReferenceField
from mongoengine.queryset import queryset_manager
from utils.decorators import cached_property, classproperty
from utils import current_enterprise_id
from app.conf import RUN_CONFIG

BASE_IGNORE_FIELDS = ['update_time']


class DocumentParseMixin:
    _restruct_ = False

    @property
    def db_dict(self):
        return self.to_mongo().to_dict()

    def to_dict(self, use_db_field=False):
        res = self.to_mongo(use_db_field=use_db_field).to_dict()
        if '_id' in res and not '_id' in self._fields_ordered:
            del res['_id']
        return res

    @property
    def db_dict(self):
        return self.to_mongo().to_dict()

    def to_dict(self, need_id=False):
        """
            将一条记录转为字典, 按需去除id
        :param need_id:
        :return:
        """
        res = self.to_mongo(use_db_field=False).to_dict()
        if not need_id and '_id' in res and '_id' not in self._fields_ordered:
            del res['_id']
        return res

    @classmethod
    def create_with(cls, data_dict, ignore_fields=BASE_IGNORE_FIELDS):
        s = cls()
        data_dict = {i: data_dict[i] for i in data_dict if i not in ignore_fields}
        Document.update_document(s, data_dict)
        return s

    def update_with(self, data_dict, ignore_fields=BASE_IGNORE_FIELDS):
        """
            利用字典内容更新对象。屏蔽掉若干字段。
        :param data_dict:
        :param ignore_fields:
        :return:
        """
        data_dict = {i: data_dict[i] for i in data_dict if i not in ignore_fields}
        Document.update_document(self, data_dict)

    @staticmethod
    def update_document(document, data_dict):
        def field_value(field, value):
            if field.__class__ in (fields.ListField, fields.SortedListField):
                return [
                    field_value(field.field, item)
                    for item in value
                ]
            if field.__class__ in (
                    fields.ReferenceField,
            ) and type(value) in (ObjectId, str):
                return field.document_type.objects.get(pk=value)
            elif field.__class__ in (
                    fields.EmbeddedDocumentField,
                    fields.ReferenceField,
                    fields.GenericEmbeddedDocumentField,
                    fields.GenericReferenceField
            ):
                if hasattr(field.document_type, 'create_with') and field.document_type._restruct_:
                    return field.document_type.create_with(value)
                else:
                    return field.document_type(**value)
            else:
                return value

        for key, value in data_dict.items():
            try:
                field = document._fields[key]
                if isinstance(field, (EmbeddedDocumentField, ReferenceField)) and value is None:
                    continue
                else:
                    setattr(
                        document, key,
                        field_value(field, value)
                    )
            except Exception as e:
                print('err field %s - value %s' % (key, str(value)))
                raise Exception('err field %s - value %s' % (key, str(value)))


class DataDocument(OriDocument, DocumentParseMixin):
    """资源数据"""
    meta = {
        'abstract': True
    }

    @classproperty
    def _class_name_(cls):
        return cls.__module__ + '.' + cls.__name__

    @classmethod
    def get_or_create(cls, **kwargs):
        try:
            return cls.objects.get(**kwargs)
        except:
            return cls(**kwargs).save()

    def set_cached_property(self, k, v):
        """
            在行数据上存缓存。然后利用如下方式获取缓存数据。
            @cached_property
            def k(self):
                return v
        :param k:
        :param v:
        :return:
        """
        self.__dict__[k] = v

    def clear_cached_property(self, k):
        """
            清除对应的缓存数据
        :param k:
        :param v:
        :return:
        """
        if k in self.__dict__:
            del self.__dict__[k]


class Document(DataDocument):
    """业务数据"""
    enterprise_id = StringField()
    creator_id = ObjectIdField(db_field='creator')
    # todo 暂时用字符串，未来可能集体替换为 ObjectIdField updator同理。
    create_time = DateTimeField()
    updator_id = ObjectIdField(db_field='updator')
    update_time = DateTimeField()
    is_deleted = BooleanField(required=False)
    delete_time = DateTimeField()
    meta = {
        'abstract': True
    }
    ignore_fields = BASE_IGNORE_FIELDS

    @queryset_manager
    def ori_objects(doc_cls, queryset):
        """原始"""
        return queryset

    @queryset_manager
    def across_objects(doc_cls, queryset):
        """跨公司"""
        return queryset.filter(is_deleted__ne=True)

    @queryset_manager
    def include_del_objects(doc_cls, queryset):
        """含删除"""
        return queryset.filter(enterprise_id=str(current_enterprise_id))

    @queryset_manager
    def objects(doc_cls, queryset):
        """限制公司和删除"""
        return queryset.filter(enterprise_id=str(current_enterprise_id)). \
            filter(is_deleted__ne=True)

    @property
    def _company_(self):
        from app.enterprise.models import EnterPrise
        return EnterPrise.objects.get(name=self.enterprise_id)

    @cached_property
    def creator(self):
        if self.creator_id:
            from app.user.models import User
            return User.ori_objects.get(id=self.creator_id)

    @cached_property
    def updator(self):
        if self.updator_id:
            from app.user.models import User
            return User.ori_objects.get(id=self.updator_id)

    def ori_save(self, **kwargs):
        return super(Document, self).save(**kwargs)

    def save(self, **kwargs):
        from flask_login import current_user
        if current_user and not current_user.is_anonymous:
            now_user_id = current_user.id
        else:
            now_user_id = None
        if not self.creator_id:
            self.creator_id = now_user_id
        if not self.enterprise_id:
            self.enterprise_id = str(current_enterprise_id)
        if not self.create_time:
            self.create_time = datetime.datetime.utcnow()
        if self.pk:
            self.update_time = datetime.datetime.utcnow()
            self.updator_id = now_user_id
        return super(Document, self).save(**kwargs)

    def syn_es_save(self, mongo_save=True, **kwargs):
        '''
            1.mongo ==> es
            2.使用序列化器内部递归遍历的方式把内部嵌套的ObjectID类型变成str,无需递归
            3.为了保持删除也能同步一开始把所有的字段设置为None 然后 用 dict update的机制来更新字段
            保存到mongo中并且同步一份数据到es里,es中如果不存在mongo中对应的数据,也会同步一份过去
            4.mongo_save 来控制是否需要在mongo中保存一份数据,如果初始化数据同步,设置成False即可
            5.可自定制序列化器,把mongo中有的内容以及拓展的内容写入es,而不存在于mongo中
        '''
        if mongo_save:
            obj = self.save(**kwargs)
        if not self.es_meta.get("es_mapping"):
            raise Exception("表单中需要在meta参数内设置es映射")

        es_mapping = self.es_meta.get("es_mapping")
        es_serializer_mapping = self.es_meta.get("es_serializer_mapping")

        # 默认序列化器与自定制序列化器的选择
        class Serializer(ModelSerializer):
            class Meta:
                model = self.__class__
                fields = "__all__"

        if es_serializer_mapping:
            Serializer = es_serializer_mapping
            Serializer.Meta.model = self.__class__

        # 从mongo中序列化出es需要的数据结构
        fields = es_mapping.get_fields()
        meta_data = {item: None for item in self.__class__._fields.keys()}
        meta_data.update(Serializer(self).data)
        data = {"_id": str(meta_data.get("id"))}
        data.update({key: meta_data.get(key) for key in meta_data if key in fields})
        es_mapping(**data).save()

        if mongo_save:
            return obj

    def set_deleted(self):
        self.is_deleted = True
        self.delete_time = datetime.datetime.utcnow()
        from flask_login import current_user
        if current_user and not current_user.is_anonymous:
            self.updator = current_user.id
        self.save()


class DataMixDocument(Document):
    """
        混合数据：初始了资源数据，随后可以由用于新增数据
    """
    meta = {
        'abstract': True
    }

    @queryset_manager
    def objects(doc_cls, queryset):
        return queryset.filter(Q(enterprise_id=str(current_enterprise_id))|Q(company_id__exists=False)). \
            filter(is_deleted__ne=True)

    def save(self, **kwargs):
        from flask_login import current_user
        if current_user and not current_user.is_anonymous:
            now_user_id = current_user.id
            now_company_id = current_user.enterprise_id
        else:
            now_user_id = None
            now_company_id = None
        if self.pk:
            self.update_time = datetime.datetime.utcnow()
            self.updator_id = now_user_id
        else:
            if not self.creator_id:
                self.creator_id = now_user_id
            if not self.enterprise_id:
                self.enterprise_id = now_company_id
            if not self.create_time:
                self.create_time = datetime.datetime.utcnow()
        return super(Document, self).save(**kwargs)
